// Copyright 2011-2012, Vanya Davidenko.
// Используемая кодировка: UTF-8.

#ifndef LIBV_BOOST_XML_XML_DOCUMENT_H_
#define LIBV_BOOST_XML_XML_DOCUMENT_H_

#include <string>
#include <exception>
#include <libv/boost/shared_ptr.h>
#include <libv/boost/xml/xml_node.h>
#include <libv/check.h>
#include <libv/uncopyable.h>
#include "rapidxml.h"



namespace libv {

// Исключение, генерируемое при ошибке разбора xml-данных.
class XmlParseException : public ::std::exception {
  public:
    XmlParseException(const ::std::string& msg, const size_t where)
        : msg_(msg),
          where_(where) {}
    virtual ~XmlParseException() throw() {}
    virtual const char* what() const throw() { return msg_.c_str(); }
    size_t where() const throw() { return where_; }

  private:
    /*const*/ ::std::string msg_;
    /*const*/ size_t where_;
};



class XmlDocument : public XmlNode {
  public:
    enum {
        parse_no_data_nodes = rapidxml::parse_no_data_nodes,
        parse_no_element_values = rapidxml::parse_no_element_values,
        parse_no_string_terminators = rapidxml::parse_no_string_terminators,
        parse_no_entity_translation = rapidxml::parse_no_entity_translation,
        parse_no_utf8 = rapidxml::parse_no_utf8,
        parse_declaration_node = rapidxml::parse_declaration_node,
        parse_comment_nodes = rapidxml::parse_comment_nodes,
        parse_doctype_node = rapidxml::parse_doctype_node,
        parse_pi_nodes = rapidxml::parse_pi_nodes,
        parse_validate_closing_tags = rapidxml::parse_validate_closing_tags,
        parse_trim_whitespace = rapidxml::parse_trim_whitespace,
        parse_normalize_whitespace = rapidxml::parse_normalize_whitespace,
        parse_default = rapidxml::parse_default,
        parse_non_destructive = rapidxml::parse_non_destructive,
        parse_fastest = rapidxml::parse_fastest,
        parse_full = rapidxml::parse_full,
        parse_odt =
            rapidxml::parse_odt_implicit_preserve_space_in_p_tags
    };

    XmlDocument()
      : XmlNode(NULL) {
        assert(IsNull());
    }

    // По пустому вектору построится Null-документ.
    // @sa XmlNode.
    // В случае ошибки генерируется исключение XmlParseException.
    explicit XmlDocument(const ::std::string& xml_s)
      : XmlNode(NULL) {
        ::std::vector<char> buffer(xml_s.c_str(), xml_s.c_str()+xml_s.size()+1);
        ParseImpl<parse_default>(&buffer, this);
    }

    // Конструктор без лишнего копирования (swap-ает переданный аргумент).
    explicit XmlDocument(::std::vector<char>* xml_data)
      : XmlNode(NULL) {
        ParseImpl<parse_default>(xml_data, this);
    }

    ~XmlDocument() {
        assert(static_cast<XmlNode*>(this)->pimpl_ == NULL ||
               static_cast<XmlNode*>(this)->pimpl_ == doc_pimpl_.get());
    }


    template<int Tflags>
    static XmlDocument Parse(const ::std::string& xml_s) {
        ::std::vector<char> buffer(xml_s.c_str(), xml_s.c_str()+xml_s.size()+1);
        XmlDocument result;
        ParseImpl<Tflags>(&buffer, &result);
        return result;
    }

    template<int Tflags>
    static XmlDocument Parse(::std::vector<char>* xml_data) {
        XmlDocument result;
        ParseImpl<Tflags>(xml_data, &result);
        return result;
    }

  private:
    template<int Tflags>
    static void ParseImpl(::std::vector<char>* xml_data, XmlDocument* that) {
        LIBV_PRECOND(xml_data != NULL);
        LIBV_PRECOND(that != NULL);

        if ( xml_data->empty() ) {
            assert(that->IsNull());
            return;

        } else if ( xml_data->back() != '\0' ) {
            xml_data->push_back('\0');
        }

        try {
            that->xml_data_.reset(new ::std::vector<char>());
            that->xml_data_->swap(*xml_data);

            that->doc_pimpl_.reset(new rapidxml::xml_document<>());
            that->doc_pimpl_->parse<Tflags>(&(*that->xml_data_)[0]);

            static_cast<XmlNode*>(that)->pimpl_ = that->doc_pimpl_.get();

        } catch ( rapidxml::parse_error& e ) {
            const char* p = &(*that->xml_data_)[0];
            throw XmlParseException(e.what(), size_t(e.where<char>() - p));
        }
    }

    /*const*/ libv::shared_ptr< ::std::vector<char> > xml_data_;
    /*const*/ libv::shared_ptr< rapidxml::xml_document<> > doc_pimpl_;
};

}  // libv

#endif  // LIBV_BOOST_XML_XML_DOCUMENT_H_
